/*
 * Copyright 2010 Chad Retz
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */
package org.jdocng.shared.model;

import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.List;

import javax.xml.bind.annotation.XmlType;

import com.google.common.annotations.GwtCompatible;
import com.google.gson.annotations.Expose;
import com.sun.javadoc.ExecutableMemberDoc;
import com.sun.javadoc.Type;

/**
 * Implementation of {@link ExecutableMemberDoc}
 *
 * @author Chad Retz
 */
@XmlType(name = "executableMember")
@GwtCompatible(serializable = true)
@SuppressWarnings("serial")
public abstract class ExecutableMemberDocImpl extends MemberDocImpl implements ExecutableMemberDoc {

    private static String getSignature(ExecutableMemberDocImpl doc,
            boolean qualifyTypeNames) {
        //TODO code duplication w/ QDoxUtils
        StringBuilder builder = new StringBuilder("(");
        for (ParameterImpl parameter : doc.parameters) {
            if (builder.length() > 1) {
                builder.append(", ");
            }
            if (qualifyTypeNames) {
                builder.append(parameter.type().qualifiedTypeName());
            } else {
                builder.append(parameter.type().simpleTypeName());
            }
            if (parameter.type().dimension() != null) {
                builder.append(parameter.type().dimension());
            }
            builder.append(' ');
            builder.append(parameter.name());
        }
        builder.append(')');
        return builder.toString();
    }
    
    //exposed fields
    
    @Expose
    private boolean varArgs;
    
    @Expose
    private ParameterImpl[] parameters;
    
    @Expose
    private String[] thrownExceptions;
    
    @Expose
    private TypeVariableImpl[] typeParameters;
    
    //non-exposed fields
    
    private transient String flatSignatureLoaded;
    
    private transient ParamTagImpl[] paramTagsLoaded;
    
    private transient String signatureLoaded;
    
    private transient ClassDocImpl[] thrownExceptionsLoaded;
    
    private transient Type[] thrownExceptionTypesLoaded;
    
    private transient ThrowsTagImpl[] throwsTagsLoaded;
    
    private transient ParamTagImpl[] typeParamTagsLoaded;
    
    //ExecutableMemberDoc interface impl
    
    @Override
    public String flatSignature() {
        if (flatSignatureLoaded == null) {
            flatSignatureLoaded = getSignature(this, false);
        }
        return flatSignatureLoaded;
    }
    
    @Override
    public boolean isNative() {
        return Modifier.isNative(modifierSpecifier());
    }
    
    @Override
    public boolean isSynchronized() {
        return Modifier.isSynchronized(modifierSpecifier());
    }
    
    @Override
    public boolean isVarArgs() {
        return varArgs;
    }
    
    @Override
    public ParameterImpl[] parameters() {
        return parameters;
    }
    
    @Override
    public ParamTagImpl[] paramTags() {
        if (paramTagsLoaded == null) {
            List<ParamTagImpl> paramTags = new ArrayList<ParamTagImpl>();
            for (TagImpl tag : tags()) {
                if (tag instanceof ParamTagImpl &&
                        !((ParamTagImpl) tag).isTypeParameter()) {
                    paramTags.add((ParamTagImpl) tag);
                }
            }
            paramTagsLoaded = paramTags.toArray(new ParamTagImpl[0]);
        }
        return paramTagsLoaded;
    }
    
    @Override
    public String signature() {
        if (signatureLoaded == null) {
            signatureLoaded = getSignature(this, true);
        }
        return signatureLoaded;
    }
    
    @Override
    public ClassDocImpl[] thrownExceptions() {
        if (thrownExceptionsLoaded == null) {
            thrownExceptionsLoaded = getContext().getClassDocs(thrownExceptions);
        }
        return thrownExceptionsLoaded;
    }
    
    @Override
    public Type[] thrownExceptionTypes() {
        if (thrownExceptionTypesLoaded == null) {
            thrownExceptionTypesLoaded = getContext().getTypes(thrownExceptions);
        }
        return thrownExceptionTypesLoaded;
    }
    
    @Override
    public ThrowsTagImpl[] throwsTags() {
        if (throwsTagsLoaded == null) {
            List<ThrowsTagImpl> throwsTags = new ArrayList<ThrowsTagImpl>();
            for (TagImpl tag : tags()) {
                if (tag instanceof ThrowsTagImpl) {
                    throwsTags.add((ThrowsTagImpl) tag);
                }
            }
            throwsTagsLoaded = throwsTags.toArray(new ThrowsTagImpl[0]);
        }
        return throwsTagsLoaded;
    }
    
    @Override
    public TypeVariableImpl[] typeParameters() {
        return typeParameters;
    }
    
    @Override
    public ParamTagImpl[] typeParamTags() {
        if (typeParamTagsLoaded == null) {
            List<ParamTagImpl> typeParamTags = new ArrayList<ParamTagImpl>();
            for (TagImpl tag : tags()) {
                if (tag instanceof ParamTagImpl &&
                        ((ParamTagImpl) tag).isTypeParameter()) {
                    typeParamTags.add((ParamTagImpl) tag);
                }
            }
            typeParamTagsLoaded = typeParamTags.toArray(new ParamTagImpl[0]);
        }
        return typeParamTagsLoaded;
    }
    
    //other accessors
    
    public void setVarArgs(boolean varArgs) {
        this.varArgs = varArgs;
    }
    
    public void setParameters(ParameterImpl[] parameters) {
        this.parameters = parameters;
    }
    
    public String[] getThrownExceptions() {
        return thrownExceptions;
    }
    
    public void setThrownExceptions(String[] thrownExceptions) {
        this.thrownExceptions = thrownExceptions;
    }
    
    public void setTypeParameters(TypeVariableImpl[] typeParameters) {
        this.typeParameters = typeParameters;
    }
    
}
